#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

#include <err.h>
#include <sysexits.h>

#include <zahl.h>

#include "dc.h"
#include "util.h"

static int dc(struct dc *, FILE *);

int
main(int argc, char *argv[])
{
	struct dc d;
	jmp_buf env;
	FILE *fp;
	int needinput = 1;
	int c;

	(void)setvbuf(stdout, NULL, _IOLBF, 0);
	if (setjmp(env) != 0)
		abort();
	zsetup(env);
	dcinit(&d);
	while ((c = getopt(argc, argv, "e:f:")) != -1)
		switch (c) {
		case 'e':
			if ((fp = fmemopen(optarg, strlen(optarg),
			    "r")) == NULL)
				err(EX_NOINPUT, "fmemopen");
			(void)dc(&d, fp);
			fclose(fp);
			needinput = 0;
			break;
		case 'f':
			if ((fp = fopen(optarg, "r")) == NULL)
				err(EX_NOINPUT, "Could not open %s",
				    optarg);
			(void)dc(&d, fp);
			fclose(fp);
			needinput = 0;
			break;
		default:
			goto usage;
		}
	argc -= optind;
	argv += optind;
	if (argc > 0)
		for (; *argv != NULL; argv++) {
			if ((fp = fopen(*argv, "r")) == NULL)
				err(EX_NOINPUT, "Could not open %s",
				    optarg);
			(void)dc(&d, fp);
			fclose(fp);
		}
	else if (needinput)
		(void)dc(&d, stdin);
	dcfree(&d);
	zunsetup();
	return (0);
usage:
	dcfree(&d);
	zunsetup();
	(void)fprintf(stderr,
	    "usage: dc [-e commands] [-f file] [file ...]\n");
	return (EX_USAGE);
}

int
dc(struct dc *d, FILE *fp)
{
	struct dcent e[1];
	char *p;
	int c;
	int r;
	int v;

	for (;;)
		switch (c = fgetc(fp)) {
		case EOF:
			if ((fp = dcret_pop(&d->rstack)) == NULL)
				goto done;
			break;
		case '\t':
			break;
		case '\n':
			if (!(d->flags & DCNL))
				break;
			d->flags &= ~DCNL;
			if ((fp = dcret_pop(&d->rstack)) == NULL)
				goto done;
			break;
		case '\f':
			break;
		case '\r':
			break;
		case ' ':
			break;
		case '!':
			switch ((c = fgetc(fp))) {
			case EOF:
				goto eof;
			case '<':
				/* FALLTHROUGH */
			case '=':
				/* FALLTHROUGH */
			case '>':
				if ((r = fgetc(fp)) == EOF)
					goto eof;
				if (d->reg[r].type != DCSTR)
					errx(EX_DATAERR,
					    "`%c' is not a string", r);
				if (dccmp(d, &v) != 0 || v == c - '=')
					break;
				if ((p = memdup(d->reg[r].u.s.str,
				    d->reg[r].u.s.len)) == NULL ||
				    dcret_push(&d->rstack, fp, p) != 0
				    || (fp = fmemopen(p,
				    d->reg[r].u.s.len, "r")) == NULL)
					err(EX_SOFTWARE,
					    "Failed on `%c'", c);
				break;
			default:
				ungetc(c, fp);
				p = NULL;
				if (getdelim(&p, NULL, '\n', fp) > 0) {
					(void)system(p);
					free(p);
				}
				break;
			}
			break;
		case '#':
			while ((c = fgetc(fp)) != EOF && c != '\n')
				/* do nothing */;
			if ((d->flags & DCNL) &&
			    (fp = dcret_pop(&d->rstack)) == NULL)
				goto done;
			break;
		case '%':
			dcmod(d);
			break;
		case '[':
			(void)dcstr(d, fp);
			break;
		case '*':
			dcmul(d);
			break;
		case '+':
			dcadd(d);
			break;
		case '-':
			dcsub(d);
			break;
		case '/':
			dcdiv(d);
			break;
		case '0':
			/* FALLTHROUGH */
		case '1':
			/* FALLTHROUGH */
		case '2':
			/* FALLTHROUGH */
		case '3':
			/* FALLTHROUGH */
		case '4':
			/* FALLTHROUGH */
		case '5':
			/* FALLTHROUGH */
		case '6':
			/* FALLTHROUGH */
		case '7':
			/* FALLTHROUGH */
		case '8':
			/* FALLTHROUGH */
		case '9':
			ungetc(c, fp);
			(void)dcnum(d, fp);
			break;
		case '<':
			/* FALLTHOURGH */
		case '=':
			/* FALLTHOURGH */
		case '>':
			if ((r = fgetc(fp)) == EOF)
				goto eof;
			if (d->reg[r].type != DCSTR)
				errx(EX_DATAERR, "`%c' is not a string",
				    r);
			if (dccmp(d, &v) != 0 || v != c - '=')
				break;
			if ((p = memdup(d->reg[r].u.s.str,
			    d->reg[r].u.s.len)) == NULL ||
			    dcret_push(&d->rstack, fp, p) != 0 ||
			    (fp = fmemopen(p, d->reg[r].u.s.len, "r"))
			    == NULL)
				err(EX_SOFTWARE, "Failed on `%c'", c);
			break;
		case '?':
			if (d->flags & DCNL) {
				warnx("Invalid use of `?'");
				break;
			}
			d->flags |= DCNL;
			if (dcret_push(&d->rstack, fp, NULL) != 0 ||
			    (fp = fdopen(STDIN_FILENO, "r")) == NULL)
				err(EX_SOFTWARE, "Failed on `%c'", c);
			break;
		case 'A':
			/* FALLTHOURGH */
		case 'B':
			/* FALLTHOURGH */
		case 'C':
			/* FALLTHOURGH */
		case 'D':
			/* FALLTHOURGH */
		case 'E':
			/* FALLTHOURGH */
		case 'F':
			ungetc(c, fp);
			(void)dcnum(d, fp);
			break;
		case 'I':
			dcpushi(d);
			break;
		case 'K':
			dcpushk(d);
			break;
		case 'L':
			if ((r = fgetc(fp)) == EOF)
				goto eof;
			if (d->reg[r].type != DCSTACK) {
				warnx("`%c' is not a stack", r);
				break;
			}
			if (dcent_pop(&d->reg[r].u.d, 0, e, 1) != 0 ||
			    dcent_push(&d->stack, e, 1) != 0)
				warnx("Failed on `%c'", c);
			break;
		case 'O':
			dcpusho(d);
			break;
		case 'P':
			if (dcprintc(d, stdout) != 0)
				goto done;
			break;
		case 'Q':
			if (dcent_pop(&d->stack, DCNUM, e, 1) != 0)
				break;
			for (;;) {
				zsub(e[0].u.n.v, e[0].u.n.v,
				    e[0].u.n.k);
				if (zcmp(e[0].u.n.v, e[0].u.n.k) == -1)
					break;
				fclose(fp);
				if ((fp = dcret_pop(&d->rstack))
				    == NULL)
					goto done;
			}
			break;
		case 'S':
			if ((r = fgetc(fp)) == EOF)
				goto eof;
			if (d->reg[r].type != DCSTACK) {
				dcent_free(&d->reg[r]);
				d->reg[r].type = DCSTACK;
				(void)memset(&d->reg[r].u.d, 0,
				    sizeof(struct data));
				d->reg[r].u.d.size =
				    sizeof(struct dcent);
			}
			if (dcent_pop(&d->stack, 0, e, 1) != 0 ||
			    dcent_push(&d->reg[r].u.d, e, 1) != 0)
				warnx("Failed on `%c'", c);
			break;
		case 'Z':
			dcpushl(d);
			break;
		case '^':
			dcpow(d);
			break;
		case '_':
			c = fgetc(fp);
			ungetc(c, fp);
			if ((c >= '0' && c <= '9') ||
			    (c >= 'A' && c <= 'F'))
				dcnum(d, fp);
			dcneg(d);
			break;
		case 'c':
			dcclr(d);
			break;
		case 'd':
			dcdup(d);
			break;
		case 'e':
			if (dcprint(d, stderr) != 0)
				goto done;
			break;
		case 'f':
			if (dcprintst(d, stdout) != 0)
				goto done;
			break;
		case 'i':
			dcpopi(d);
			break;
		case 'k':
			dcpopk(d);
			break;
		case 'l':
			if ((r = fgetc(fp)) == EOF)
				goto eof;
			if (r > 255)
				abort();
			(void)dcent_dup(e, &d->reg[r]);
			(void)dcent_push(&d->stack, e, 1);
			break;
		case 'o':
			dcpopo(d);
			break;
		case 'p':
			if (dcprint(d, stdout) != 0)
				goto done;
			break;
		case 'q':
			fclose(fp);
			if ((fp = dcret_pop(&d->rstack)) == NULL)
				goto done;
			fclose(fp);
			if ((fp = dcret_pop(&d->rstack)) == NULL)
				goto done;
			break;
		case 's':
			if ((r = fgetc(fp)) == EOF)
				goto eof;
			if (r > 255)
				abort();
			if (dcent_pop(&d->stack, 0, e, 1) != 0)
				break;
			dcent_free(&d->reg[r]);
			(void)memcpy(&d->reg[r], e,
			    sizeof(struct dcent));
			break;
		case 'x':
			if (dcent_pop(&d->stack, DCSTR, e, 1) != 0)
				break;
			(void)dcret_push(&d->rstack, fp, e[0].u.s.str);
			if ((fp = fmemopen(e[0].u.s.str, e[0].u.s.len,
			    "r")) == NULL)
				err(EX_DATAERR, "Failed on `%c'", c);
			break;
		case 'v':
			dcsqrt(d);
			break;
		case 'z':
			dcpushz(d);
			break;
		default:
			warnx("`%c' not implemented", c);
			break;
		}
done:
	warnx("done");
	fclose(fp);
	return (0);
eof:
	warnx("Unexpected EOF");
	return (0);
}
